package com.tool.emulator.apple2.ui;

import com.tool.emulator.apple2.*;
import com.tool.emulator.apple2.ui.StatusBar.StatusConf;
import com.tool.emulator.mos6502.CPU6502;
import com.tool.emulator.mos6502.debug.ui.DebugMain;
import com.tool.emulator.mos6502.debug.ui.DebugUtil;

import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

/**
 * MonitorPane.java @author tl 2011-6-19
 */
public class MonitorPane extends JPanel implements MonitorUI, KeyboardUI {
	public static final int FAST_SAVE = KeyEvent.VK_F1;
	static final Color[] dispColors = {
			null, Color.WHITE, Color.GREEN, Color.RED, Color.CYAN};
	static final long MAX_FREQ = 1000 * 1000 * 1000;
	static final long MIN_FREQ = 1;
	static final double speed_factor = Math.sqrt(2);
	public final Apple2 apple2;
	public final DebugMain debug;
	final StatusBar status = new StatusBar();
	final JLabel disk = status.addStatusComponent("磁盘");
	final JLabel main = status.addStatusComponent("CPU");
	final JLabel rec = status.addStatusComponent("录音机");
	final JLabel speaker = status.addStatusComponent("喇叭");
	final JLabel printer = status.addStatusComponent("打印机");
	final StatusConf normal = status.newStatusConf(null, null, null);
	final StatusConf busy_green = status.newStatusConf(null, Color.GREEN, null);
	final StatusConf busy_red = status.newStatusConf(null, Color.RED, Color.WHITE);
	final StatusConf playing = status.newStatusConf("正在播放", Color.GREEN, null);
	final StatusConf recing = status.newStatusConf("正在录音", Color.RED, Color.WHITE);
	final StatusConf recIdle = status.newStatusConf("录音机", null, null);
	//private final MonitorUI screen = new TextPanel(this);
	private final MonitorUI screen = new DisplayPanel();
	final AbstractAction changeColor = new AbstractAction("显示器颜色") {
		private volatile int clrIndex = 0;

		public void actionPerformed(ActionEvent e) {
			clrIndex = (clrIndex + 1) % dispColors.length;
			changeColor(dispColors[clrIndex]);
		}

	};
	//static final Font font = new Font("Dialog",0,18);
	volatile boolean recBusy = false;
	private volatile Keyboard kb;
	private volatile long timestamp = 0;
	private long last_cycles = 0;
	private long last_time = 0;
	private String tempStatus = null;
	private long tempTimeout = 0;
	private final Timer timer = new Timer(300, new ActionListener() {

		public void actionPerformed(ActionEvent e) {
			refreshStatus(apple2.CPU);
		}

	});
	private File lastPath = new File(".");
	private File lastSelectedFile = null;
	final AbstractAction recRec = new AbstractAction("录制磁带") {
		public void actionPerformed(ActionEvent e) {
			if (recBusy) return;
			File file = selectOpenFile("选择保存的文件", "开始录制", "rec");
			if (file != null) {
				try {
					DataOutputStream out = Util.saveFile(file, "A2REC");
					apple2.RECORDER.startRec(out);
					enableRecs(true);
				} catch (Exception ex) {
					onError(ex);
				}
			}
			setFocus();
		}
	};
	final AbstractAction recStop = new AbstractAction("停止") {
		{
			this.setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			if (!recBusy) return;
			apple2.RECORDER.stop();
			enableRecs(false);
			setFocus();
		}
	};
	final AbstractAction recPlay = new AbstractAction("播放磁带") {
		public void actionPerformed(ActionEvent e) {
			if (recBusy) return;
			File file = selectOpenFile("选择要播放的文件", "播放", "rec");
			if (file != null) {
				try {
					DataInputStream in = Util.openFile(file, "A2REC");
					apple2.RECORDER.startPlay(in);
					enableRecs(true);
					//sendCommand("E000G\nLOAD");
				} catch (Exception ex) {
					onError(ex);
				}
			}
			setFocus();
		}
	};
	private File lastDumpFile = null;

	{
		timer.start();
	}

	public MonitorPane(final Apple2 apple2) {
		super();
		this.apple2 = apple2;
		this.setLayout(new BorderLayout());
		JToolBar tb = new JToolBar();
		tb.add(changeColor);
		tb.addSeparator();
		//tb.setFont(font);
		//status.setFont(font);
		final CPU6502 cpu = apple2.CPU;
		tb.add(new AbstractAction("加速") {
			public void actionPerformed(ActionEvent e) {
				long freq = (long) (cpu.getFreq() * speed_factor);
				if (freq > MAX_FREQ) freq = MAX_FREQ;
				cpu.setFreq(freq);
				setFocus();

			}

		});
		tb.add(new AbstractAction("减速") {
			public void actionPerformed(ActionEvent e) {
				long freq = (long) (cpu.getFreq() / speed_factor);
				if (freq < MIN_FREQ) freq = MIN_FREQ;
				cpu.setFreq(freq);
				setFocus();
			}

		});
		tb.addSeparator();

		tb.add(new AbstractAction("重启") {
			public void actionPerformed(ActionEvent e) {
				++timestamp;
				try {
					apple2.reset();
				} catch (Exception ex) {
					onError(ex);
				}
				last_time = 0;
				setFocus();
			}

		});
		tb.add(new AbstractAction("暂停/恢复") {
			public void actionPerformed(ActionEvent e) {
				try {
					if (apple2.getEmulator().isPaused())
						apple2.getEmulator().resume();
					else
						apple2.getEmulator().pause();
				} catch (Exception ex) {
					onError(ex);
				}
				setFocus();
			}

		});

		tb.addSeparator();
		final String[] title = new String[1];
		tb.add(new AbstractAction("载入磁盘") {
			public void actionPerformed(ActionEvent e) {
				if (title[0] == null) title[0] = UIUtil.findComponent(MonitorPane.this, Frame.class).getTitle();
				++timestamp;
				try {
					File file = selectOpenFile("选择Apple2磁盘文件", "载入", "dsk");
					if (file != null) {
						apple2.DISK.loadDisk(0, file);
						//apple2.reset();
						//sendCommand("C600G");
						UIUtil.findComponent(MonitorPane.this, Frame.class).setTitle(title[0] + " - " + file.getName());
					}
				} catch (Exception ex) {
					onError(ex);
				}
				last_time = 0;
				setFocus();
			}

		});
		tb.add(new AbstractAction("卸载磁盘") {

			public void actionPerformed(ActionEvent e) {
				apple2.DISK.uninstall();
				if (title[0] != null)
					UIUtil.findComponent(MonitorPane.this, Frame.class).setTitle(title[0]);
			}

		});
		tb.add(new AbstractAction("载入脚本") {
			public void actionPerformed(ActionEvent e) {
				try {
					File file = selectOpenFile("选择脚本文件", "载入", "bas", "asm", "txt");
					if (file != null)
						sendCommand(file);
				} catch (Exception ex) {
					onError(ex);
				}
				setFocus();
			}
		});
		tb.addSeparator();
		tb.add(new AbstractAction("保存镜像") {
			public void actionPerformed(ActionEvent e) {
				saveDump(false);
			}

		});
		tb.add(new AbstractAction("快速保存镜像(F1)") {
			public void actionPerformed(ActionEvent e) {
				saveDump(true);
			}

		});
		tb.add(new AbstractAction("载入镜像") {
			public void actionPerformed(ActionEvent e) {
				try {
					File file = selectOpenFile("选择要载入的镜像文件", "载入", "bin");
					if (file != null) {
						++timestamp;
						apple2.restoreFromFile(file);
						lastDumpFile = file;
						setTempStatus("镜像成功载入");
					}

				} catch (Exception ex) {
					onError(ex);
				}
				last_time = 0;
				setFocus();
			}

		});
		tb.addSeparator();
		tb.add(recPlay);
		tb.add(recStop);
		tb.add(recRec);
		this.add(tb, BorderLayout.NORTH);
		this.add((Component) screen.getViewComponent(), BorderLayout.CENTER);
		this.add(status, BorderLayout.SOUTH);
		this.setFocusable(true);
		this.setRequestFocusEnabled(true);
		this.addKeyListener(new KeyAdapter() {

			@Override
			public void keyPressed(KeyEvent e) {
				keyboardInput(e);
			}

		});
		this.addMouseListener(new MouseAdapter() {

			@Override
			public void mousePressed(MouseEvent e) {
				setFocus();
			}

		});
		tb.setFloatable(false);
		for (Component c : tb.getComponents())
			c.setFocusable(false);
		this.debug = new DebugMain(this, apple2.BUS);
		tb.add(new AbstractAction("Debug") {
			public void actionPerformed(ActionEvent e) {
				debug.debuger.install();
				DebugUtil.activeComponent(debug);
			}

		});
	}

	private void enableRecs(boolean busy) {
		this.recBusy = busy;
		recPlay.setEnabled(!busy);
		recRec.setEnabled(!busy);
		recStop.setEnabled(busy);
	}

	private void setFocus() {
		if (this.isFocusOwner())
			return;
		boolean b = this.requestFocusInWindow();
		if (!b)
			System.err.println("request focus failed.");

	}

	private void keyboardInput(KeyEvent e) {
		if (e.getKeyCode() == FAST_SAVE) {
			this.saveDump(true);
			return;
		}
		Keyboard kb = this.kb;
		if (kb == null)
			return;
		int input = mapInput(e.getKeyChar(), e.getKeyCode());
		if (input > 0 && input <= 255)
			kb.setInput(input);
	}

	public void sendCommand(final File file) {
		final long tm = ++this.timestamp;
		new Thread() {
			public void run() {
				try {
					FileReader fin = new FileReader(file);
					try {
						BufferedReader in = new BufferedReader(fin);
						for (; ; ) {
							String line = in.readLine();
							if (line == null)
								break;
							if (line.replace('\t', ' ').trim().startsWith("//"))
								continue;
							if (tm != timestamp)
								return;
							sendLine(line);
						}
					} finally {
						fin.close();
					}
				} catch (Exception ex) {
					onError(ex);
				}
			}
		}.start();
	}

	public void sendCommand(final String s) {
		new Thread() {
			public void run() {
				try {
					sendLine(s);
				} catch (Exception ex) {
					onError(ex);
				}
			}
		}.start();
	}

	private boolean sendLine(String s) throws Exception {
		if (s != null) {
			for (int i = 0; i < s.length(); i++) {
				if (!sendInput(s.charAt(i), 0))
					return false;
			}
		}
		sendInput((char) 0xffff, KeyEvent.VK_ENTER);
		return true;
	}

	private boolean sendInput(char c, int code) throws Exception {
		Keyboard kb = this.kb;
		if (kb == null) return false;
		int val = mapInput(c, code);
		if (val < 0) return true;
		while (!kb.canInput()) {
			Thread.sleep(1);
		}
		kb.setInput(val);
		return true;
	}

	private int mapInput(char c, int code) {
		switch (code) {
			case KeyEvent.VK_UP:
				return 11;
			case KeyEvent.VK_DOWN:
				return 10;
			case KeyEvent.VK_LEFT:
				return 8;
			case KeyEvent.VK_RIGHT:
				return 21;
			case KeyEvent.VK_ENTER:
				return 13;
			case KeyEvent.VK_TAB:
				return 9;
			case KeyEvent.VK_DELETE:
				return 127;
		}
		if (c <= 255) {
			if (c == '\r')
				return -1;
			if (c == '\n')
				c = 13;
			return c;
		}
		return -1;
	}

	public void install(Keyboard kb) {
		this.kb = kb;
	}

	public void uninstall(Keyboard kb) {
		this.kb = null;
	}

	public void refresh(Monitor m) {
		this.screen.refresh(m);
	}

	void setTempStatus(String text) {
		this.setTempStatus(text, 3000L);
	}

	void setTempStatus(String text, long timeout) {
		tempStatus = text;
		tempTimeout = System.currentTimeMillis() + timeout;
		status.setStatus(text);
	}

	void refreshStatus(CPU6502 cpu) {
		if (last_time == 0) {
			last_cycles = cpu.getCycles();
			last_time = System.currentTimeMillis();
			return;
		}

		long cycles = cpu.getCycles();
		long time = System.currentTimeMillis();
		long cd = cycles - last_cycles;
		long td = time - last_time;
		long freq = cd * 1000L / td;
		float rate = 100 * (float) freq / (float) cpu.getFreq();
		if (tempStatus != null && tempTimeout > System.currentTimeMillis())
			status.setStatus(tempStatus);
		else
			status.setStatus("CPU Freq: " + freq + "/" + cpu.getFreq() + " " + rate + "%");
		this.last_cycles = cycles;
		this.last_time = time;
		this.enableRecs(!apple2.RECORDER.isStoped());
		if (apple2.getEmulator().isPaused() || debug.debuger.isSuspended())
			status.setStatus(main, busy_red);
		else
			status.setStatus(main, busy_green);
		if (apple2.RECORDER.isPlaying())
			status.setStatus(rec, playing);
		else if (apple2.RECORDER.isRecording())
			status.setStatus(rec, recing);
		else
			status.setStatus(rec, recIdle);
		status.setStatus(speaker, apple2.SPEAKER.isBusy() ? busy_red : normal);
		if (apple2.DISK.isInstalled())
			status.setStatus(disk, apple2.DISK.isBusy() ? busy_red : busy_green);
		else
			status.setStatus(disk, normal);
		if (apple2.PRINTER.isActive())
			status.setStatus(printer, apple2.PRINTER.isBusy() ? busy_red : busy_green);
		else
			status.setStatus(printer, normal);
	}

	public Object getViewComponent() {
		return this;
	}

	File selectOpenFile(String title, String okText, String... fileTypes) {
		return this.selectFile(JFileChooser.OPEN_DIALOG, title, okText, fileTypes);
	}

	File selectSaveFile(String title, String okText, String... fileTypes) {
		return this.selectFile(JFileChooser.SAVE_DIALOG, title, okText, fileTypes);

	}

	File selectFile(int type, String title, String okText, final String... fileTypes) {
		File file = null;
		JFileChooser fc = new JFileChooser(lastPath);
		fc.setDialogType(type);
		fc.setDialogTitle(title);
		if (fileTypes != null && fileTypes.length > 0) {
			StringBuilder sb = new StringBuilder();
			sb.append("*." + fileTypes[0]);
			for (int i = 1; i < fileTypes.length; i++)
				sb.append("|*." + fileTypes[i]);
			final String desc = sb.toString();
			FileFilter filter = new FileFilter() {
				public boolean accept(File pathname) {
					if (pathname.isDirectory()) return true;
					String name = pathname.getName().toLowerCase();
					for (String ext : fileTypes) {
						if (name.endsWith(("." + ext).toLowerCase()))
							return true;
					}
					return false;
				}

				@Override
				public String getDescription() {
					return desc;
				}

			};
			fc.setFileFilter(filter);
			if (this.lastSelectedFile != null && filter.accept(this.lastSelectedFile))
				fc.setSelectedFile(this.lastSelectedFile);
		} else {
			if (lastSelectedFile != null)
				fc.setSelectedFile(lastSelectedFile);
		}

		if (fc.showDialog(this, okText) == JFileChooser.APPROVE_OPTION)
			this.lastSelectedFile = file = fc.getSelectedFile();
		this.lastPath = fc.getCurrentDirectory();
		this.setFocus();
		return file;
	}

	public void modeChanged(Monitor m) {
		this.screen.modeChanged(m);
	}

	public void changeColor(Color mono) {
		this.screen.changeColor(mono);
	}

	public void onError(final Throwable err) {
		if (!SwingUtilities.isEventDispatchThread()) {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					onError(err);
				}
			});
			return;
		}
		err.printStackTrace();
		JOptionPane.showMessageDialog(this, err);
	}

	public void saveDump(boolean fast) {
		try {
			boolean paused = apple2.getEmulator().isPaused();
			if (!paused)
				apple2.getEmulator().pause();
			try {
				File file;
				if (fast && lastDumpFile != null)
					file = lastDumpFile;
				else
					file = selectSaveFile("选择保存的镜像文件", "保存", "bin");
				if (file != null) {
					apple2.dumpToFile(file);
					lastDumpFile = file;
					this.setTempStatus("镜像已成功保存在" + file.getName());
				}
			} finally {
				if (!paused)
					apple2.getEmulator().resume();
			}
		} catch (Exception ex) {
			onError(ex);
		}
		last_time = 0;
		setFocus();
	}
}
